# Copyright 2012 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License.  You may obtain a copy
# of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.

"""Datastore models."""

import re
import urllib
import utility

from google.appengine.api import images
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import db

MAX_TEMPLATE_SIZE_BYTES = 10000000
MAX_TEMPLATE_WIDTH = 600
MAX_TEMPLATE_HEIGHT = 600


class ContestImage(db.Model):
  creation_time = db.DateTimeProperty(auto_now_add=True)
  last_modified_time = db.DateTimeProperty(auto_now=True)

  # TODO: do we want to explicitly store thumbnails?

  mime_type = db.StringProperty()
  image_data = db.BlobProperty()
  width = db.IntegerProperty()
  height = db.IntegerProperty()


class Contest(db.Model):
  # This is the "nickname" of a site admin.
  creator = db.StringProperty()
  name = db.StringProperty()
  description = db.TextProperty()
  photo_credit = db.TextProperty()
  start_time = db.DateTimeProperty()
  end_time = db.DateTimeProperty()
  disabled = db.BooleanProperty(default=False)
  image = db.ReferenceProperty(ContestImage)

  def IsCompleted(self):
    return self.end_time < utility.DateNow()

  def IsActive(self):
    now = utility.DateNow()
    return not self.disabled and self.start_time <= now and now < self.end_time

class ContestEntry(ContestImage):
  # User's IP address, or some other blob we can use to identify them.
  creator = db.StringProperty()
  contest = db.ReferenceProperty(Contest)

  topalign = db.StringProperty()
  middlealign = db.StringProperty()
  bottomalign = db.StringProperty()
  top = db.StringProperty()
  middle = db.StringProperty()
  bottom = db.StringProperty()
  font = db.StringProperty()

  score = db.IntegerProperty()
  disabled = db.BooleanProperty(default=False)

  def hasVoted(self, user):
    existing_vote = self.vote_set.ancestor(
        self.key()).filter('user =', user).get()
    if existing_vote:
      return True
    return False

  def RecordVote(self, user):
    """Record the user's vote for this entry.

    This method should only be run inside of db.run_in_transaction().
    """
    existing_vote = self.vote_set.ancestor(
        self.key()).filter('user =', user).get()
    if existing_vote:
      self.score -= 1
      existing_vote.delete()
    else:
      self.score += 1
      vote = Vote(parent=self, entry=self, user=user)
      vote.put()

    self.put()
    return self.score


class Vote(db.Model):
  # We need to record the user who voted to avoid double voting
  entry = db.ReferenceProperty(ContestEntry)
  user = db.StringProperty()


class Error(Exception):
  """Base class for all exceptions"""
  pass


class EmptyContestName(Error):
  """Indicates that the name field for a new contest was blank."""
  pass


class EmptyEntryText(Error):
  """Indicates that the user didn't enter anything."""
  pass


class CaptionGenerationFailure(Error):
  """Something really bad happened with the picker caption."""
  pass


def UploadContest(contest_name, description, username, image_data, start_time,
                  end_time, disabled, photo_credit):
  """Handles the creation of a new Contest in the DB."""
  if not contest_name:
    raise EmptyContestName()

  img = images.Image(image_data)

  if img.width > MAX_TEMPLATE_WIDTH or img.height > MAX_TEMPLATE_HEIGHT:
    img.resize(width=MAX_TEMPLATE_WIDTH, height=MAX_TEMPLATE_HEIGHT)
  else:
    img.im_feeling_lucky()

  # TODO: investigate whether we want to use PNGs for better quality.
  encoded_image = img.execute_transforms(output_encoding=images.JPEG)
  src_mime_type = 'image/jpeg'

  # OK, everything looks good; write the image.
  new_image = ContestImage(mime_type=src_mime_type,
                           image_data=encoded_image,
                           width=img.width,
                           height=img.height)
  new_image.put()

  new_contest = Contest(name=contest_name,
                        description=description,
                        creator=username,
                        image=new_image,
                        start_time=utility.ParseDate(start_time),
                        end_time=utility.ParseDate(end_time),
                        disabled=disabled,
                        photo_credit=photo_credit)

  new_contest.put()

  return new_contest.key().id()


def GetCaptionUrl(param_vars):
  """Returns the URL that can be used to fetch a caption."""
  base_url = 'https://docs.google.com/picker/caption?'
  params = 'width=%d&height=%d&font=%s&top=%s&topalign=%s&center=%s&centeralign=%s&bottom=%s&bottomalign=%s&' % (
      param_vars['width'],
      param_vars['height'],
      urllib.quote_plus(param_vars['font'].encode('utf-8')),
      urllib.quote_plus(param_vars['top'].encode('utf-8')),
      urllib.quote_plus(param_vars['topalign'].encode('utf-8')),
      urllib.quote_plus(param_vars['middle'].encode('utf-8')),
      urllib.quote_plus(param_vars['middlealign'].encode('utf-8')),
      urllib.quote_plus(param_vars['bottom'].encode('utf-8')),
      urllib.quote_plus(param_vars['bottomalign'].encode('utf-8')))
  return base_url + params


def SaveEntry(save_vars):
  """Creates a new ContestEntry."""

  if not (save_vars['top'] or save_vars['middle'] or save_vars['bottom']):
    raise EmptyEntryText

  contest_key = db.Key.from_path('Contest', int(save_vars['contest_key']))
  contest = db.get(contest_key)
  width = contest.image.width
  height = contest.image.height

  save_vars['height'] = height;
  save_vars['width'] = width;

  caption_url = GetCaptionUrl(save_vars)

  response = urlfetch.fetch(caption_url);

  if response.status_code != 200:
    raise CaptionGenerationFailure()
  else:
    composite_args = ((contest.image.image_data,
                       0,
                       0,
                       1.0,
                       images.CENTER_CENTER),
                      (response.content,
                       0,
                       0,
                       1.0,
                       images.CENTER_CENTER))
    entry_image = images.composite(composite_args,
                                   width,
                                   height,
                                   0,
                                   images.JPEG,
                                   100)

  # TODO: use get_serving_url() to provide scalable serving of image

  new_entry = ContestEntry(creator=save_vars['name'],
                           contest=contest,
                           topalign=save_vars['topalign'],
                           middlealign=save_vars['middlealign'],
                           bottomalign=save_vars['bottomalign'],
                           top=save_vars['top'],
                           middle=save_vars['middle'],
                           bottom=save_vars['bottom'],
                           font=save_vars['font'],
                           score=0,
                           mime_type='image/jpeg',
                           image_data=db.Blob(entry_image),
                           width=width,
                           height=height)

  new_entry.put()

  return new_entry.key().id()
